﻿/* MDA.Net - .Net port of mda-vst plugins
 * 
 * .Net port, Copyright (C) 2011, Balint Pfliegel
 * Based on mda-vst, Copyright (C) 1999-2001 Paul Kellett (maxim digital audio)
 * Based on VST2 SDK (c) 1996-1999 Steinberg Soft und Hardware GmbH, All Rights Reserved
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of
 * the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be
 * useful, but WITHOUT ANY WARRANTY; without even the implied
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
 * PURPOSE.  See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA.
 */

using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using MDA.Net.ResourceManagers;
using MDA.Net.Voices.Interfaces;

namespace MDA.Net.Voices
{
    public class MDAPianoVoice : IVoice
    {
        #region Constants

        static readonly int NPARAMS = 13;
        static readonly float SILENCE = 0.0001f;  //voice choking

        #endregion

        #region Fields

        // From VOICE
        Int32 delta;  //sample playback
        Int32 frac;
        Int32 pos;
        Int32 end;
        Int32 loop;

        float env;  //envelope
        float dec;

        float f0;   //first-order LPF
        float f1;
        float ff;

        float outl;
        float outr;
        Int32 note; //remember what note triggered this

        // From the synth
        float[] param = new float[NPARAMS];
        float Fs, iFs;
        Int32 cpos;
        Int32 cmax;
        float[] comb;
        float cdep;
        float width, trim;
        Int32 size;
        float fine, random, stretch;
        float muffvel, sizevel, velsens, volume;

        private bool _isPlaying = false;
        private bool _isReleased = false;

        #endregion

        #region Constructor

        public MDAPianoVoice()
        {
            // Processing
            Fs = 44100.0f; iFs = 1.0f / Fs; cmax = 0x7F;  //just in case...

            // Voice
            env = 0.0f; dec = 0.99f; //all notes off

            volume = 0.2f;
            cpos = 0;
            comb = new float[256];
        }

        #endregion

        #region IVoice Members

        public float[] Parameters
        {
            get { return param; }
        }

        public void RecalculateInternalParameters()
        {
            size = (Int32)(12.0f * param[2] - 6.0f);
            sizevel = 0.12f * param[3];
            muffvel = param[5] * param[5] * 5.0f;

            velsens = 1.0f + param[6] + param[6];
            if (param[6] < 0.25f) velsens -= 0.75f - 3.0f * param[6];

            fine = param[9];
            random = 0.077f * param[10] * param[10];
            stretch = 0.000434f * (param[11] - 0.5f);

            cdep = param[7] * param[7];
            trim = 1.50f - 0.79f * cdep;
            width = 0.04f * param[7]; if (width > 0.03f) width = 0.03f;
        }

        public void Trigger(int note, float velocity)
        {
            Array.Clear(comb, 0, comb.Length);

            _isPlaying = true;
            _isReleased = false;

            MDAKGRP[] kgrp = MDAPianoData.Instance.Keygroup;

            float l = 99.0f;
            Int32 k, s;

            k = (note - 60) * (note - 60);
            l = fine + random * ((float)(k % 13) - 6.5f);  //random & fine tune
            if (note > 60) l += stretch * (float)k; //stretch

            s = size;
            if (velocity > 40) s += (Int32)(sizevel * (float)(velocity - 40));

            k = 0;
            while (note > (kgrp[k].high + s)) k++;  //find keygroup

            l += (float)(note - kgrp[k].root); //pitch
            l = 22050.0f * iFs * (float)Math.Exp(0.05776226505 * l);

            this.delta = (Int32)(65536.0f * l);
            this.frac = 0;
            this.pos = kgrp[k].pos;
            this.end = kgrp[k].end;
            this.loop = kgrp[k].loop;

            this.env = (0.5f + velsens) * (float)Math.Pow(0.0078f * velocity, velsens); //velocity

            l = 50.0f + param[4] * param[4] * param[12] + muffvel * (float)(velocity - 64); //muffle
            if (l < (55.0f + 0.25f * (float)note)) l = 55.0f + 0.25f * (float)note;
            if (l > 210.0f) l = 210.0f;
            this.ff = l * l * iFs;
            this.f0 = this.f1 = 0.0f;

            this.note = note; //note->pan
            if (note < 12) note = 12;
            if (note > 108) note = 108;
            l = volume * trim;
            this.outr = l + l * width * (float)(note - 60);
            this.outl = l + l - this.outr;

            if (note < 44) note = 44; //limit max decay length
            l = 2.0f * param[0];
            if (l < 1.0f) l += 0.25f - 0.5f * param[0];
            this.dec = (float)Math.Exp(-iFs * Math.Exp(-0.6 + 0.033 * (double)note - l));
        }

        public void SynthesizeWithMixdown(int sampleOffset, int sampleCount, double[] leftBuffer, double[] rightBuffer, float amplitude, float pitchBend, float panAngle)
        {
            if (panAngle > 90.0f) { panAngle = 90.0f; }
            if (panAngle < -90.0f) { panAngle = -90.0f; }
            double sin_coef = Math.Sin((panAngle) * (Constants.M_PI / 180.0));
            double cos_coef = Math.Cos((panAngle) * (Constants.M_PI / 180.0));
            double xl, xr;
            double locAmp = amplitude * 3.0; // quickfix

            short[] waves = MDAPianoData.Instance.Data;
            float x, l, r;
            Int32 i;

            int z;
            for (z = sampleOffset; z < (sampleOffset + sampleCount); z++)
            {
                //integer-based linear interpolation
                frac += delta;
                pos += frac >> 16;
                frac &= 0xFFFF;
                if (pos > end) pos -= loop;

                // sample lookup
                i = waves[pos] + ((frac * (waves[pos + 1] - waves[pos])) >> 16);
                x = env * (float)i / 32768.0f;

                //envelope
                env = env * dec;

                //muffle filter
                f0 += ff * (x + f1 - f0);
                f1 = x;

                // left & right samples
                l = f0 * outl;
                r = f0 * outr;

                //stereo simulator
                comb[cpos] = l + r;
                cpos = (cpos++) & cmax;
                x = cdep * comb[cpos];

                // pan
                xl = (l + x);
                xr = (r - x);

                // mixdown
                leftBuffer[z] += (xl * cos_coef - xr * sin_coef) * locAmp;
                rightBuffer[z] += (xl * sin_coef + xr * cos_coef) * locAmp;
            }
        }

        public void Release(float velocity)
        {
            if (note < 94) //no release on highest notes
            {
                this.dec = (float)Math.Exp(-iFs * Math.Exp(2.0 + 0.017 * (double)note - 2.0 * param[1]));
            }
            _isReleased = true;
        }

        public bool IsPlaying
        {
            get
            {
                if ((_isReleased && this.env < SILENCE) || (!_isPlaying))
                {
                    return false;
                }
                return true;
            }
        }

        public bool IsReleased
        {
            get
            {
                return _isReleased;
            }
        }

        #endregion
    }
}
